#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <poll.h>
#include <ifaddrs.h>
#include <linux/if_arp.h>
#include <netdb.h>

struct _arp {
  uint16_t htype;
  uint16_t ptype;
  uint8_t hlen;
  uint8_t plen;
  uint16_t oper;
  uint8_t sha[6];
  uint8_t spa[4];
  uint8_t tha[6];
  uint8_t tpa[4];
};

int main(int argc, char **argv) {
  int sock;
  char *interface = 0;
  in_addr_t ip, sip;
  struct _arp response;
  struct ifaddrs *ifaddrs, *ifiter;
  struct sockaddr_ll *addr_ll = 0;
  struct sockaddr_in *addr_in = 0;

  if (argc < 2) {
    printf("Usage: myarp IP\n");
    return 1;
  }

  inet_aton(argv[1], &ip);
  getifaddrs(&ifaddrs); // get addresses of interfaces
  for (ifiter = ifaddrs; ifiter != NULL; ifiter = ifiter->ifa_next) {
    if (ifiter->ifa_addr->sa_family == AF_INET) {
      in_addr_t if_ip = ((struct sockaddr_in*)ifiter->ifa_addr)
          ->sin_addr.s_addr;
      in_addr_t if_mask = ((struct sockaddr_in*)ifiter->ifa_netmask)
          ->sin_addr.s_addr;
      if ((if_ip & if_mask) == (ip & if_mask)) {
        interface = ifiter->ifa_name;
        addr_in = (struct sockaddr_in*)ifiter->ifa_addr;
        break;
      }
    }
  }
  if (interface != NULL) {
    for (ifiter = ifaddrs; ifiter != NULL; ifiter = ifiter->ifa_next) {
      if (!strcmp(interface, ifiter->ifa_name) && 
          ifiter->ifa_addr->sa_family == AF_PACKET) {
        addr_ll = (struct sockaddr_ll*)ifiter->ifa_addr;
        addr_ll->sll_protocol = htons(ETH_P_ARP);
        break;
      }
    }
  }
  if (addr_ll == NULL) {
    printf("No interface is found!\n");
    return 1;
  }

  struct sockaddr_ll broadcast;
  broadcast.sll_family = AF_PACKET;
  broadcast.sll_protocol = htons(ETH_P_ARP);
  broadcast.sll_ifindex = addr_ll->sll_ifindex;
  broadcast.sll_hatype = ARPHRD_ETHER;
  broadcast.sll_pkttype = PACKET_BROADCAST;
  broadcast.sll_halen = ETH_ALEN;
  memset(broadcast.sll_addr, 0xFF, ETH_ALEN);

  struct _arp request;
  request.htype = htons(0x0001);
  request.ptype = htons(0x0800);
  request.hlen = 6;
  request.plen = 4;
  request.oper = htons(1);
  memcpy(request.sha, addr_ll->sll_addr, 6);
  memcpy(request.spa, &addr_in->sin_addr, 4);
  memset(request.tha, 0, ETH_ALEN);
  memcpy(request.tpa, &ip, 4);

  if ((sock = socket(AF_PACKET, SOCK_DGRAM, ETH_P_ARP)) < 0) {
    perror("socket");
    return errno;
  }
  if (bind(sock, (struct sockaddr*)addr_ll, sizeof(struct sockaddr_ll)) < 0) {
    perror("bind");
    return 1;
  }
  if (sendto(sock, &request, sizeof(struct _arp), 0, 
             (struct sockaddr*)&broadcast, sizeof(struct sockaddr_ll)) < 0) {
    perror("sendto");
    return 1;
  }
  if (recv(sock, &response, sizeof(struct _arp), 0) < 0) {
    perror("recv");
    return 1;
  }
  if ((response.oper == htons(2)) && *((in_addr_t*)&response.spa) == ip) {
    printf("%02X:%02X:%02X:%02X:%02X:%02X\n", 
           response.sha[0], response.sha[1], response.sha[2],
           response.sha[3], response.sha[4], response.sha[5]);
  } else {
    printf("Wrong host!\n");
  }
  freeifaddrs(ifaddrs);
  close(sock);
  return 0;
}
